package com.dotflat.rest.service.impl;

import com.dotflat.rest.PO.AreableObjectBasePO;
import com.dotflat.rest.domain.AreableObjectBase;
import com.dotflat.rest.service.AreableObjectBaseService;
import com.dotflat.rest.utils.*;
import com.matt.commons.exception.BusinessException;
import com.matt.foundation.model.ErrorCode;
import com.matt.foundation.utils.CopyUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;


@Service
public abstract class AreableObjectBaseServiceImpl<T1 extends AreableObjectBase, T2 extends JpaSpecificationExecutor<T1> & JpaRepository<T1,String>>
        implements AreableObjectBaseService<T1> {

    protected T2 baseRepository;
    public void setBaseRepository(T2 baseRepository) {
        this.baseRepository = baseRepository;
    }

    private boolean permissionCheck(T1 object, String areaId){
        if (object == null || StringUtils.isEmpty(areaId)){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在");
        }

        String targetAreaId = object.getAreaId();
        if (StringUtils.isEmpty(object.getAreaId())){
            //公共对象
            return true;
        }
        if (StringUtils.isEmpty(targetAreaId)
                || !areaId.equals(targetAreaId)){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在!");
        }

        return true;
    }

    @Override
    public T1 update(T1 t){
        return baseRepository.save(t);
    }

    @Override
    public T1 get(String areaId, String staffId, String id) {
        return get(areaId, staffId, id, true);
    }

    public T1 get(String areaId, String staffId, String id, boolean withCount) {
        if (StringUtils.isEmpty(areaId) || StringUtils.isEmpty(id)){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在");
        }

        T1 t = baseRepository.getOne(id);

        try {
            permissionCheck(t, areaId);
        }catch (RuntimeException e){
            t = null;
        }

        if (t == null){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在");
        }

        if (withCount && t.getClass().isAnnotationPresent(CountableType.class)){
            //处理自动计数类型
            boolean modified = false;
            for (Field field : t.getClass().getDeclaredFields()) {
                if (!field.isAnnotationPresent(CountableField.class)){
                    continue;
                }

                //获取授权
                field.setAccessible(true);

                //属性名称
                String fieldName = field.getName();

                //属性的值
                try {
                    Object fieldValue = field.get(t);
                    if (fieldValue == null){
                        fieldValue = 1;
                    }

                    if (fieldValue.getClass() == Integer.class){
                        field.setInt(t, (Integer)fieldValue + 1);
                        modified = true;
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            if (modified){
                baseRepository.save(t);
            }
        }

        return t;
    }


    protected void generatePagePredicates(Root<T1> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder,
                                          AreableObjectBasePO param, List<Predicate> predicates)  {
        generatePagePredicates(root, query, criteriaBuilder, param, predicates, false);
    }

    protected void generatePagePredicates(Root<T1> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder,
                                          AreableObjectBasePO param, List<Predicate> predicates, boolean uniqueFilter/*重复检查*/)  {

        //反射获取Class的属性（Field表示类中的成员变量）
        Class clazz = null;
        do{
            if (clazz == null){
                clazz = param.getClass();
            }else{
                clazz = clazz.getSuperclass();
            }
            Field[] fileds = clazz.getDeclaredFields();
            //获取最新数据，解决部分更新时jpa其他字段设置null问题
            for (Field field : fileds) {

                //获取授权
                field.setAccessible(true);

                //属性名称
                String fieldName = field.getName();

                //属性的值
                Object fieldValue = null;
                try {
                    fieldValue = field.get(param);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    return;
                }

                //处理值
                if (uniqueFilter){
                    if (!field.isAnnotationPresent(UniqueField.class)) {
                        //过滤唯一条件
                        continue;
                    }

                    //指定唯一约束字段值
                    UniqueField ann = field.getAnnotation(UniqueField.class);
                    if (StringUtils.isNotBlank(ann.value())){
                        fieldValue = getVal(ann.value(), field.getType());
                    }
                }

                //找出值为空的属性，值为空则为忽略属性，或者被NotFound标注，我们复制的时候不进行赋值
                if(fieldValue != null){
                    if ((fieldValue instanceof String && StringUtils.isEmpty((String)fieldValue))){
                        //空字符串直接略过
                        continue;
                    }

                    if (field.isAnnotationPresent(LikeableField.class) && !uniqueFilter) {
                        //模糊查找
                        Predicate p = criteriaBuilder.like(root.get(fieldName), "%" + fieldValue + "%");
                        predicates.add(p);
                    }else if (field.isAnnotationPresent(QueryInField.class)) {
                        QueryInField ann = field.getAnnotation(QueryInField.class);
                        if (StringUtils.isNotBlank(ann.value()) && fieldValue != null && fieldValue instanceof Iterable) {
                            Iterable iter = (Iterable)fieldValue;
                            if (iter.iterator().hasNext()) {
                                CriteriaBuilder.In<Object> inClause = criteriaBuilder.in(root.get(ann.value()));
                                for (Object o : iter) {
                                    inClause.value(o);
                                }

                                predicates.add(inClause);
                            }
                        }
                    }else if (field.isAnnotationPresent(RangeField.class)) {
                        RangeField ann = field.getAnnotation(RangeField.class);

                        if (StringUtils.isNotBlank(ann.value()) && fieldValue != null && fieldValue instanceof Comparable) {
                            Predicate p = null;
                            if (ann.operator() == '<' && ann.eq()){
                                p = criteriaBuilder.lessThanOrEqualTo(root.get(ann.value()), (Comparable)fieldValue);
                            }else if (ann.operator() == '<' && !ann.eq()){
                                p = criteriaBuilder.lessThan(root.get(ann.value()), (Comparable)fieldValue);
                            }else if (ann.operator() == '>' && !ann.eq()){
                                p = criteriaBuilder.greaterThan(root.get(ann.value()), (Comparable)fieldValue);
                            }else if (ann.operator() == '>' && ann.eq()){
                                p = criteriaBuilder.greaterThanOrEqualTo(root.get(ann.value()), (Comparable)fieldValue);
                            }else {
                                throw new BusinessException(ErrorCode.VERSION_ERROR, "意料之外的运算符");
                            }

                            predicates.add(p);
                        }
                    }else if("areaId".equals(fieldName)) {
                        //areaId 为空时也需要查出来
                        Predicate p1 = criteriaBuilder.equal(root.get(fieldName), fieldValue);
                        if (uniqueFilter){
                            //重复检查时不需要检查areaId为空的情况
                            predicates.add(p1);
                        }else {
                            Predicate p2 = criteriaBuilder.isNull(root.get(fieldName));
                            predicates.add(criteriaBuilder.or(p1, p2));
                        }
                    }else{
                        //精确
                        Predicate p = criteriaBuilder.equal(root.get(fieldName), fieldValue);
                        predicates.add(p);
                    }
                }
            }
        }while (clazz != AreableObjectBasePO.class && clazz != Object.class && clazz != null);

    }

    @Override
    public Page<T1> page(String areaId, String staffId, AreableObjectBasePO param, PageRequest pageRequest) {

        if (StringUtils.isNotBlank(areaId)) {
            param.setAreaId(areaId);
        }
        param.setDeleted(false);

        Specification<T1> specification = new Specification<T1>() {
            @Override
            public Predicate toPredicate(Root<T1> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();

                generatePagePredicates(root, query, criteriaBuilder, param, predicates);

                query.where(predicates.toArray(new Predicate[predicates.size()]));
                return query.getRestriction();
            }
        };

        Page<T1> page = baseRepository.findAll(specification, pageRequest);

        parsePageResult(areaId, staffId, param, pageRequest, page);

        return page;
    }

    protected void parsePageResult(String areaId, String staffId, AreableObjectBasePO param, PageRequest pageRequest, Page<T1> page){

    }

    @Override
    public void delete(String areaId, String staffId, AreableObjectBasePO param) {

        if (StringUtils.isEmpty(param.getId())){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在");
        }

        T1 t = get(areaId, staffId, param.getId());

        if (t == null){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "记录不存在");
        }

        t.setDeleted(true);

        baseRepository.save(t);
    }

    @Override
    public void delete(String areaId, String staffId, String[] ids){
        if (ids == null || ids.length == 0){
            throw new BusinessException(ErrorCode.PARAM_CAN_NOT_BLANK, "参数错误");
        }

        List<T1> items = baseRepository.findAllById(Arrays.asList(ids));
        if (items.size() != ids.length){
            throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在");
        }

        for (T1 t: items){
            if (!t.getAreaId().equals(areaId)){
                throw new BusinessException(ErrorCode.MATERIAL_NOT_EXIST, "对象不存在!");
            }

            t.setDeleted(true);
        }

        baseRepository.saveAll(items);
    }

    public boolean duplicateCheck(AreableObjectBasePO param){

        if (!param.getClass().isAnnotationPresent(UniqueType.class)){
            return false;
        }
        if (null !=param.getId()){
            return false;
        }
        Specification<T1> specification = new Specification<T1>() {
            @Override
            public Predicate toPredicate(Root<T1> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();

                generatePagePredicates(root, query, criteriaBuilder, param, predicates, true);

//                if (predicates.size() == 0){
//                    throw
//                }

                query.where(predicates.toArray(new Predicate[predicates.size()]));

                return query.getRestriction();
            }
        };

        List result = baseRepository.findAll(specification);

        return result != null && result.size() > 0;
    }

    @Override
    public T1 add(String areaId, String staffId, AreableObjectBasePO param) {
        return add(areaId, staffId, param, null);
    }


    public <T> T getVal(String val, Class<T> type) {
        // 把val转换成type类型返回 比如说getVal("123",Integer.class) 返回一个123
        T value = null;
        try {
            Constructor<T> constructor = type.getConstructor(String.class);
            constructor.setAccessible(true);
            value = constructor.newInstance(val);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return value;
    }

    @Override
    public T1 add(String areaId, String staffId, AreableObjectBasePO param, Consumer<? super T1> preAction) {

        param.setAreaId(areaId);

        ParameterizedType ptype = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class clazz = (Class<T1>) ptype.getActualTypeArguments()[0];

        if (duplicateCheck(param)){
            throw new BusinessException(ErrorCode.RECORD_DUPLICATED, "数据记录重复");
        }

        try {
            T1 o = null;
            if (StringUtils.isEmpty(param.getId())){
                o = (T1) clazz.newInstance();
            } else {
                o = get(areaId, staffId, param.getId(), false);
            }

            //找到只读的字段
            List<Field> fields = FieldUtils.getFieldsListWithAnnotation(param.getClass(), ReadonlyField.class);
            if (fields.size() > 0){
                List<String> fieldNames = fields.stream().map(f->f.getName()).collect(Collectors.toList());
                fieldNames.add("id");
                fieldNames.add("areaId");
                String[] fieldArr = fieldNames.stream().toArray(String[]::new);

//                BeanUtils.copyProperties(param, o, fieldArr);
                CopyUtils.copyPropertiesSkipNullAndExternalProperties(param, o, fieldArr);
            }else{
                CopyUtils.copyPropertiesSkipNullAndExternalProperties(param, o, new String[]{
                        "id", "areaId"
                });
//                BeanUtils.copyProperties(param, o, "id", "areaId");
            }


            if (preAction != null) {
                preAction.accept(o);
            }

            o.setDeleted(false);
            o.setAreaId(areaId);
            o.setCreated(new Date());
            o.setCreatedBy(staffId);
            if (null == o.getAudited()) {
                o.setAudited(0);
            }

            baseRepository.save(o);

            return o;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        throw new BusinessException(ErrorCode.UNDEFINED, "模板对象错误");
    }
}
